
#include <stdlib.h>
#include <apr-1/apr_random.h>
#include "../iface/APRInterface.h"
#include "APRPRNG.h"

#ifdef __WIN32__
#include <windows.h>
#pragma comment(lib, "advapi32.lib")
#endif

// Need to somehow force destruction of this when the APR library unloads..!
namespace APRPRNGImplementation
{

class APRPRNGData
{
   public:
      APRPRNGData()
      {
         apr_status_t status;
         status = apr_pool_create(&mp, NULL);
         rng_algo = apr_random_standard_new(mp);
      }
      ~APRPRNGData()
      {
         apr_pool_destroy(mp);
      }
      apr_random_t *getWorkingRNG();
   private:
      apr_random_t *rng_algo;
      apr_pool_t *mp;
};

};

#define PRNG_DATA_BLOCK 4096

apr_random_t *APRPRNGImplementation::APRPRNGData::getWorkingRNG()
{
#ifdef PURIFY
   unsigned char *ranbuf;
   if (apr_random_secure_ready(rng_algo) == APR_ENOTENOUGHENTROPY)
   {
      ranbuf = (unsigned char *)malloc(sizeof(unsigned char)*PRNG_DATA_BLOCK);
      FILE *FP = fopen("/dev/urandom", "rb");
      if (FP)
      {
         int elems = fread(ranbuf, 1, PRNG_DATA_BLOCK, FP);
         fclose(FP);
      }
      apr_random_add_entropy(rng_algo, ranbuf, PRNG_DATA_BLOCK);
      free(ranbuf);
   }
#endif
#ifdef __WIN32__
   int win32success = 0;
   if (apr_random_secure_ready(rng_algo) == APR_ENOTENOUGHENTROPY)
   {
      ranbuf = (unsigned char *)malloc(sizeof(unsigned char)*PRNG_DATA_BLOCK);
      HCRYPTPROV hProvider = 0;

      if (!::CryptAcquireContextW(&hProvider, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
         break;

      if (!::CryptGenRandom(hProvider, PRNG_DATA_BLOCK, ranbuf))
      {
         ::CryptReleaseContext(hProvider, 0);
         break;
      }
      
      win32success = 1;
      !::CryptReleaseContext(hProvider, 0));
      
      apr_random_add_entropy(rng_algo, ranbuf, PRNG_DATA_BLOCK);
      free(ranbuf);
   }
#endif
   const int maximum = 1024*1024*64;
   int bs = PRNG_DATA_BLOCK;
   while ((apr_random_secure_ready(rng_algo) == APR_ENOTENOUGHENTROPY) && (bs <= maximum))
   {
      ranbuf = (unsigned char *)malloc(sizeof(unsigned char)*bs);
      apr_random_add_entropy(rng_algo, ranbuf, bs);
      free(ranbuf);
      bs *= 2;
   }
   return rng_algo;
}


APRPRNGImplementation::APRPRNGData *global_data = NULL;

APRPRNGImplementation::APRPRNGData* getInstance()
{
   if (global_data == NULL)
   {
      global_data = new APRPRNGImplementation::APRPRNGData();
   }
   return global_data;
}

APRPRNG::APRPRNG()
{
   data = getInstance();
}

void APRPRNG::forceDestruction()
{
   APRPRNGImplementation::APRPRNGData *old_data_pointer = global_data;
   global_data = NULL;
   delete old_data_pointer;
}

char *APRPRNG::getRandomBytes(size_t bytes)
{
   apr_status_t status;
   apr_random_t *rng = data->getWorkingRNG();
   char *databuffer = (char *)malloc(sizeof(char)*bytes);
   status = apr_random_secure_bytes(rng, databuffer, bytes);
   CheckAPRError(status);
   return databuffer;
}
